#include "Llamadas.h"

extern BufferCache * cache;

extern TDFU tdfu; //para un proceso
extern StructFile * structFile; //para todo el sistema (mem compartida)


IdBloque bmap(int nstruct, int offset) { //dado offset de fichero (punteroIO) -> devuelve numero de bloque
   //obviamos comprobacion de rango
   IdBloque idb;
   idb.fs = structFile->entries[nstruct].fs;
   idb.inodo = structFile->entries[nstruct].inodo;
   idb.bloque = floor(offset / TAM_BLOQUE);
   return idb;
}

void tdfu_init() { //reserva las 3 primeras entradas para:
   //99 por poner algo
   tdfu.entries[0].fd_real = 99; //entrada estandar (stdin)
   tdfu.entries[0].pStructFile = 99; //XXX: se podria mejorar para saber que fd son

   tdfu.entries[1].fd_real = 99; //salida estandar (stdout)
   tdfu.entries[1].pStructFile = 99;
 
   tdfu.entries[2].fd_real = 99; //error estandar (stderr)
   tdfu.entries[2].pStructFile = 99; 

   tdfu.tam = 3;

   for (int i = 3; i < MAX_TDFU; i++)
      tdfu_setNull(i);
}


void tdfu_setNull(int nt) { //pone entrada todo a NIL
   /* if ((nt < 0) || (nt >= MAX_TDFU)) {
      printf("pid = %d. ERROR tdfu_isNull\n", getpid());
      return;
   }*/

   tdfu.entries[nt].fd_real = NIL;
   tdfu.entries[nt].pStructFile = NIL;

   tdfu.tam--;
}

bool tdfu_isNull(int nt) { //true si entrada es NIL
   /* if ((nt < 0) || (nt >= MAX_TDFU)) {
      printf("pid = %d. ERROR tdfu_isNull\n", getpid());
      return false;
   } */

   return tdfu.entries[nt].pStructFile == NIL; //suficiente con esta comprobacion
}

void struct_setNull(int nstruct) { //pone entrada todo a NIL
   /* if ((nstruct < 0) || (nstruct >= MAX_STRUCT_FILE)) {
      printf("pid = %d. ERROR struct_setNull\n", getpid());
      return;
   } */

   structFile->entries[nstruct].o_permisos = NIL;
   structFile->entries[nstruct].p_io = NIL;
   structFile->entries[nstruct].fs = NIL;
   structFile->entries[nstruct].inodo = NIL;
   structFile->entries[nstruct].tamFich = NIL;

   structFile->tam--; //solo sirve para controlar llenado.  XXX: mejora: podria hacer falta contador de veces q se abre un mismo fichero
}

bool struct_isNull(int nstruct) { //true si entrada es NIL
   /* if ((nstruct < 0) || (nstruct >= MAX_STRUCT_FILE)) {
      printf("pid = %d. ERROR struct_isNull\n", getpid());
      return false;
   } */

   return structFile->entries[nstruct].inodo == NIL; //suficiente con esta comprobacion
}

void struct_init() {
   structFile->tam = 0;
   for (int i = 0; i < MAX_STRUCT_FILE; i++)
      struct_setNull(i);
}


int get_pTdfu(int fd) { //devuelve entrada de TDFU que tenga fd
   if (tdfu.tam == 0)
      return NIL;

   int i = 0;
   while (1) { //busca primera entrada de la TDFU libre
      if (i == MAX_TDFU)
         return NIL;

      if (tdfu.entries[i].fd_real == fd) //encontrada
         return i;

      i++;
   }
}


int get_pStructFile(int fd) { //dado descriptor de fichero, devuelve puntero a struct file
   int i = get_pTdfu(fd);
   if (i == NIL)
      return NIL; //no encontrada
   else
      return tdfu.entries[i].pStructFile; //encontrada. puede dar NIL
}



void struct_print() { //imprime la Struct File
   printf("\nSTRUCT FILE: \n");
   for (int i = 0; i < MAX_STRUCT_FILE; i++)
      if (struct_isNull(i))
         printf("[null], ");
      else
         printf("[i=%d, perm=%d, io=%d], ", \
               structFile->entries[i].inodo, structFile->entries[i].o_permisos, structFile->entries[i].p_io);

   printf("\n");
}

void tdfu_print() { //imprime la TDFU
   printf("\nTDFU: \n");
   for (int i = 0; i < MAX_TDFU; i++)
      if (tdfu_isNull(i))
         printf("[null], ");
      else
         printf("[fd=%d, nstruct=%d], ", tdfu.entries[i].fd_real, tdfu.entries[i].pStructFile);
   printf("\n");
}

//**********************************************************************************************

int abrir(const char * path, int modo) { //abre fichero. devuelve fd
   chivato("pid = %d. inicio abrir\n", getpid());
   struct stat estado;
   //const char * cadena = path.c_str(); //c_str devuelve const *char del string
   if ((stat(path, & estado) < 0) && (modo != O_CREAR)) { //devuelve estado de fichero en el struct estado
      printf("Error al abrir el fichero %s: No existe\n", path);
      return NIL;
   }
   
   //3) comprobar permisos y hacer open. en terminal: stat -c %a namefich
   int fd;

   switch (modo) {
      case O_READ: 
         if (access(path, R_OK) < 0) { //access devuelve 0 si este fichero tiene permisos de lectura. si no tiene: < 0
            printf("Error al abrir el fichero %s: Permiso de lectura denegado\n", path);
            return NIL;
         }
         fd = open(path, O_RDONLY);
         break;

      case O_WRITE: 
         if (access(path, W_OK) < 0) {
            printf("Error al abrir el fichero %s: Permiso de escritura denegado\n", path);
            return NIL;
         }
         fd = open(path, O_WRONLY);
         break;

      case O_RW: 
         if (access(path, R_OK | W_OK) < 0) {
            printf("Error al abrir el fichero %s: Permiso de lectura/escritura denegado\n", path);
            return NIL;
         }
         fd = open(path, O_RDWR);
         break;

      case O_CREAR: 
         fd = open(path, O_RDWR | O_CREAT, 0777); //O_EXCL. aguita para encontrar el epico ultimo error: era READONLY xDD
         if (fd < 0) { //fallo open
            printf("Error al abrir el fichero %s: modo O_CREAR: ya existía el fichero\n", path);
            return NIL;
         }
         break;

      default:
         printf("Error al abrir el fichero %s: Permisos especificados erroneamente\n", path);
         return NIL;
   } //switch

   if (fd < 0) { //fallo open
      printf("Error al abrir el fichero %s: Fallo en open\n", path);
      return NIL;
   }

   //1) convertir pathname a inodo
   if (stat(path, & estado) < 0) { //devuelve estado de fichero en el struct estado
      printf("Error al abrir el fichero %s: Esto no deberia pasar!\n", path);
      return NIL;
   }
   
   int fs = estado.st_dev; //fyle system
   int inodo = estado.st_ino; // en terminal: stat -c %i namefich
   int tamFich = estado.st_size; //tamaño de fichero

   //2) cargar nodo a memoria: nada
   // 4) Asignar entradas de tablas

   int sem = semAt(SEM_ID_STRUCT); //coge semaforo (mem compartida)
   semWait(sem);

   /* antes era asi: 
   structFile->entries[structFile->tam].o_permisos = modo;
   structFile->entries[structFile->tam].fs = fs;
   structFile->entries[structFile->tam].inodo = inodo;
   structFile->entries[structFile->tam].p_io = 0; //se podria mejorar con modo append
   structFile->entries[structFile->tam].tamFich = tamFich; 
   tdfu.entries[tdfu.tam].fd_real = fd;
   tdfu.entries[tdfu.tam].pStructFile = nstruct; */

   //asignar StructFile
   if (structFile->tam == MAX_STRUCT_FILE) { //aqui esta la utilidad de tam xDD
      printf("Error al abrir el fichero %s: STRUCTFILE LLENA: superado maximo de ficheros (%d) \n", path, MAX_STRUCT_FILE);
      semSignal(sem);
      return NIL;
   }

   int nstruct = 0;
   while (1) { //busca primera entrada de structFile libre
      if (nstruct == MAX_STRUCT_FILE) { //se supone que aqui no entraria, si eso seria justo arriba
         printf("Error al abrir el fichero %s: STRUCTFILE LLENA: superado maximo de ficheros. No encuentra hueco. (%d) \n", path, MAX_STRUCT_FILE);
         semSignal(sem);
         return NIL;
      }

      if (struct_isNull(nstruct)) {
         structFile->entries[nstruct].o_permisos = modo;
         structFile->entries[nstruct].fs = fs;
         structFile->entries[nstruct].inodo = inodo;
         structFile->entries[nstruct].p_io = 0; //XXX: se podria mejorar con modo append
         structFile->entries[nstruct].tamFich = tamFich;

         structFile->tam++;
         break;
      }

      nstruct++;
   }


   //asignar TDFU
   if (tdfu.tam == MAX_TDFU) { //aqui esta la utilidad de tam xDD
      printf("Error al abrir el fichero %s: TDFU LLENA: superado maximo de ficheros (%d) \n", path, MAX_TDFU);
      struct_setNull(nstruct); //liberamos la entrada de la structFile recien creada
      semSignal(sem);
      return NIL;
   }
   
   int nt = 0;
   while (1) { //busca primera entrada de structFile libre
      if (nt == MAX_TDFU) { //se supone que aqui no entraria, si eso seria justo arriba
         printf("Error al abrir el fichero %s: TDFU LLENA: superado maximo de ficheros. No encuentra hueco (%d) \n", path, MAX_TDFU);
         struct_setNull(nstruct); //liberamos la entrada de la structFile recien creada
         semSignal(sem);
         return NIL;
      }

      if (tdfu_isNull(nt)) {
         tdfu.entries[nt].fd_real = fd;
         tdfu.entries[nt].pStructFile = nstruct;
         tdfu.tam++;
         break;
      }

      nt++;
   }

   //XXX: 5) if (modo es truncate) liberar todos los bloques del fichero

   chivato("exmut struct: pid = %d. fin abrir\n", getpid());
   semSignal(sem);
   return fd;
}


int leer(int fd, char * buffer, int count) { //lee desde cache -> buffer de usuario (si no en cache, lee de disco y actualiza cache)
   if (count <= 0)
      return 0;

   chivato("pid = %d. inicio leer: count=%d\n", getpid(), count);
   //1) Accede a struct file a partir de fd
   int nstruct = get_pStructFile(fd);

   //2) comprueba permisos de lectura
   if ((structFile->entries[nstruct].o_permisos != O_READ) && (structFile->entries[nstruct].o_permisos != O_RW)) {
      printf("Error de permisos al leer fichero\n");
      return NIL;
   }

   //3 Inicializa variables locales
   int bytesRest = count; //bytes restantes
   char * mem; //posicion de memoria que se va moviendo por buffer para copiar
   mem = buffer; //empieza en la primera posicion
   int p_io2 = structFile->entries[nstruct].p_io;
   int initPos = p_io2;

   //4) bucle para copiar mem
   while (bytesRest > 0) { //&& ( != EOF)
      IdBloque idb = bmap(nstruct, p_io2);

      int nbuf = bread(fd, idb.fs, idb.inodo, idb.bloque);

      int offset = p_io2 % TAM_BLOQUE; //calcular offset dentro del bloque (es el resto de bmap)
      int bytesRead;
      if (cache->bufs[nbuf].nbytesValidos <= offset)
         bytesRead = 0;
      else
         bytesRead = cache->bufs[nbuf].nbytesValidos - offset; //calcular numero de bytes a leer (los que quedan del bloque)

      if (bytesRest < bytesRead) //si lo q se quiere leer es menos todavia
         bytesRead = bytesRest;

      chivato("pid = %d. leer: pio2=%d, nbuf=%d, bloque=%d, offset=%d, nbytesValidos=%d -> bytesRead=%d\n", \
      getpid(), p_io2, nbuf, idb.bloque, offset, cache->bufs[nbuf].nbytesValidos, bytesRead);
      if (bytesRead == 0) { //llega a EOF
         brelse(nbuf); //agarrate para haberse dado cuenta de esto eh xDD
         break;
      }
      
      memcpy(mem, cache->bufs[nbuf].datos + offset, bytesRead); //copia desde cache (kernel) a mem (usuario), 
      //bread ya hace: buf.fd = fd, buf.datosValidos = 1, buf.nbytes_Validos = ...
      brelse(nbuf);

      bytesRest -= bytesRead;
      p_io2 += bytesRead;
      mem += bytesRead; //aritmetica de punteros. se mueve por buffer

      int sem = semAt(SEM_ID_STRUCT); //coge semaforo (mem compartida)
      semWait(sem);
      structFile->entries[nstruct].p_io = p_io2;//actualiza puntero E/S 
      semSignal(sem);  
   }

   chivato("pid = %d. fin leer. pio=%d\n", getpid(), structFile->entries[nstruct].p_io);
   return p_io2 - initPos; //numero total de bytes leidos. si es 0: FINFICH
}


int escribir(int fd, char * buffer, int count) {  //escribe buffer de usuario en bufferCache
   if (count <= 0)
      return 0;

   chivato("pid = %d. inicio escribir: buf=%s, count=%d\n", getpid(), buffer, count);
   //1) Accede a struct file a partir de fd
   int nstruct = get_pStructFile(fd);

   //2) comprueba permisos de lectura
   if ((structFile->entries[nstruct].o_permisos != O_WRITE) && (structFile->entries[nstruct].o_permisos != O_RW) \
         && (structFile->entries[nstruct].o_permisos != O_CREAR)) {
      printf("Error de permisos al escribir fichero\n");
      return NIL;
   }

   //3 Inicializa variables locales
   int bytesRest = count; //bytes restantes
   char * mem; //posicion de memoria que se va moviendo por buffer para copiar
   mem = buffer; //empieza en la primera posicion
   int p_io2 = structFile->entries[nstruct].p_io;
   int initPos = p_io2;

   //4) bucle para copiar mem
   while (bytesRest > 0) { //&& ( != EOF)
      IdBloque idb = bmap(nstruct, p_io2);

      int offset = p_io2 % TAM_BLOQUE; //calcular offset dentro del bloque (es el resto de bmap)
      //para escribir no hay que controlar EOF xq se puede aumentar el tam del fichero al escribir

      int bytesWrite = TAM_BLOQUE - offset; //calcular numero de bytes a leer (los que quedan del bloque)

      if (bytesRest < bytesWrite) //si lo q se quiere escribir es menos todavia
         bytesWrite = bytesRest;

      if (bytesWrite == 0)
         break;
      
      
      int nbuf;
      bool read = false;
      if ((offset == 0) && (bytesRest >= TAM_BLOQUE))
         nbuf = getblk(idb.fs, idb.inodo, idb.bloque); //se hara escritura total del bloque
      else {
         nbuf = bread(fd, idb.fs, idb.inodo, idb.bloque); //se hara escritura parcial del bloque
         read = true;
      }

      int sem = semAt(SEM_ID_CACHE); //coge semaforo buffer cache (mem compartida)
      semWait(sem);
      
      chivato("pid = %d. escribir: p_io2=%d, inodo=%d, bloque=%d, offset=%d, nbuf=%d, nValidos=%d, bytesWrite=%d \n", \
             getpid(), p_io2, idb.inodo, idb.bloque, offset, nbuf, cache->bufs[nbuf].nbytesValidos, bytesWrite);
      /*for (int i = 0; i < cache->bufs[nbuf].nbytesValidos; i++)
         printf("%c", cache->bufs[nbuf].datos[i]);
      printf(". mem=");
      for (int i = 0; i < bytesWrite; i++)
         printf("%c", mem[i]);
      printf("\n");*/
      
      memcpy(cache->bufs[nbuf].datos + offset, mem, bytesWrite); //copia desde mem (usuario) a cache (kernel)
      cache->bufs[nbuf].datosValidos = 1;
      if ( (!read) || ( ((read) && (offset + bytesWrite > cache->bufs[nbuf].nbytesValidos))) )
         cache->bufs[nbuf].nbytesValidos = offset + bytesWrite; 
      cache->bufs[nbuf].fd = fd;
      
      //-- bwrite:
      cache->bufs[nbuf].pid = getpid();
      cache->bufs[nbuf].delayWrite = 1; 
      //--

      semSignal(sem); //hay que hacer esto para que no hayan dos semWait() seguidos

      //bwrite(nbuf); //dw = 1

      bytesRest -= bytesWrite;
      p_io2 += bytesWrite;
      mem += bytesWrite; //aritmetica de punteros. se mueve por buffer

      int sem2 = semAt(SEM_ID_STRUCT); //coge semaforo struct file (mem compartida)
      semWait(sem2);
      structFile->entries[nstruct].p_io = p_io2; //actualiza puntero E/S
      semSignal(sem2);
      
      brelse(nbuf);
   }

   chivato("pid = %d. fin escribir. pio=%d\n", getpid(), structFile->entries[nstruct].p_io);
   return p_io2 - initPos; //numero total de bytes leidos. si es 0: FINFICH
}

void cerrar(int fd) { //cierra fichero
   chivato("pid = %d. inicio cerrar\n", getpid());
   int semCache = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
   int semStruct = semAt(SEM_ID_STRUCT); //coge semaforo (mem compartida)
   semWait(semCache);
   semWait(semStruct);

   for (int i = 0; i < MAX_BUFFERS; i++) //recorre toda la bufferCache
      if ((cache->bufs[i].pid == getpid()) && (cache->bufs[i].fd == fd) && (cache->bufs[i].delayWrite)) {
            //hace el trabajo de bajo nivel que hace my_sigusr2()
            lseek(cache->bufs[i].fd, cache->bufs[i].idbloque.bloque * TAM_BLOQUE, 0);

            chivato("exmut pid = %d. cerrar: escritura de disco desde nbuf=%d, datos=%s\n en lseek=%d", \
                   getpid(), i, cache->bufs[i].datos, cache->bufs[i].idbloque.bloque * TAM_BLOQUE);
            write(fd, cache->bufs[i].datos, cache->bufs[i].nbytesValidos); //disco <- buf.datos //TAM_BLOQUE
            //chivato("exmut cerrar: nbytesValidos=%d, nwriten=%d\n", cache->bufs[i].nbytesValidos, nwriten);
            cache->bufs[i].delayWrite = 0;
            //cache->bufs[i].nbytesValidos = nwriten; //bueno, si se hizo antes bread, no haria falta, xq seria igual
            cache->bufs[i].datosValidos = 1;
            //brelse(i); //no hace falta, siempre se libera en leer o escribir         
      }

   //libera entradas de StructFile y TDFU. tiene que ser en este orden
   struct_setNull(get_pStructFile(fd)); 
   tdfu_setNull(get_pTdfu(fd));

   close(fd); //XXX: mejora: controlar que lo cierre cuando terminen todos

   chivato("exmut pid = %d. fin cerrar\n", getpid());
   semSignal(semCache);
   semSignal(semStruct);

}

int posicionar(int fd, int offset, int posRef) { //mueve puntero de I/O
   chivato("pid = %d. inicio posicionar\n", getpid());
   int pStruct = get_pStructFile(fd);
   if (pStruct == NIL)
      return NIL; //error

   int sem = semAt(SEM_ID_STRUCT); //coge semaforo (mem compartida)
   semWait(sem);
   structFile->entries[pStruct].p_io = posRef + offset;
   chivato("exmut struct: pid = %d. fin posicionar\n", getpid());
   semSignal(sem);
   return 0; //sin error
}


